#include "riscv.h"
#include "mmap.h"
#include "trap.h"
#include "config.h"
#include "proc.h"
#include "atomic.h"
#include "spinlock.h"
#include "memory.h"
#include "file.h"
#include "syscall.h"
#include "elf.h"
#include "spinlock.h"
#include "frontend.h"
#include <string.h>
#include "fs.h"
#include "fat32.h"

struct proc proc[NPROC];
struct cpu cpus[NCPU];
spinlock_t pid_lock;
int nextpid = 1;
struct proc *initproc;

int
cpuid()
{
  int id = r_tp();
  return id;
}


// Return this CPU's cpu struct.
// Interrupts must be disabled.
struct cpu*
mycpu(void) {
  int id = cpuid();
  struct cpu *c = &cpus[id];
  return c;
}

// Return the current struct proc *, or zero if none.
struct proc*
myproc(void) {
  // printk("in myproc\n");
  push_off();
  struct cpu *c = mycpu();
  struct proc *p = c->proc;
  pop_off();
  // printk("in myproc out\n");
  return p;
}

// initialize the proc table at boot time.
void
procinit(void)
{
  struct proc *p;
  initlock(&pid_lock, "nextpid");
  for(p = proc; p < &proc[NPROC]; p++) {
      initlock(&p->lock, "proc");
      p->kstack = pmm_manager.alloc_pages(1);
  }
}

int
allocpid() {
  int pid;

  acquire(&pid_lock);
  pid = nextpid;
  nextpid = nextpid + 1;
  release(&pid_lock);

  return pid;
}

// Create a user page table for a given process,
// with no user memory, but with trampoline pages.
pagetable_t
proc_pagetable(struct proc *p)
{
  pagetable_t pagetable;

  // An empty page table.
  pagetable = uvmcreate();
  if(pagetable == 0)
    return 0;
  extern char trap_sec_start;


  // map the trampoline code (for system call return)
  // at the highest user virtual address.
  // only the supervisor uses it, on the way
  // to/from user space, so not PTE_U.
  if(mappages(pagetable, (uint64)&trap_sec_start, PGSIZE,
              (uint64) &trap_sec_start, prot_to_type(PROT_READ|PROT_EXEC,0)) < 0){
    // uvmfree(pagetable, 0);
    // return 0;
    panic("proc_pagetable mappages trap_sec_start fail\n");
  }

  // map the trapframe just below TRAMPOLINE, for trampoline.S.
  if(mappages(pagetable, (uint64)(p->trapframe), PGSIZE,
              (uint64)(p->trapframe),prot_to_type( PROT_WRITE|PROT_READ,0)) < 0){
    // uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    // uvmfree(pagetable, 0);
    // return 0;
      panic("proc_pagetable trapframe trap_sec_start fail\n");
  }
  // uvmprint(pagetable);
  if(mappages(pagetable, USER_STACK-PGSIZE, PGSIZE*2,
            pmm_manager.alloc_pages(2), prot_to_type(PROT_WRITE|PROT_READ,1)) < 0){
  // uvmfree(pagetable, 0);
  // return 0;
  panic("proc_pagetable mappages USER_STACK fail\n");
  }

  p->trapframe->sp = USER_STACK;
  return pagetable;
}


// A fork child's very first scheduling by scheduler()
// will swtch to forkret.
void
forkret(void)
{
  static int first = 1;

  // Still holding p->lock from scheduler.
  release(&myproc()->lock);

  if (first) {
    // File system initialization must be run in the context of a
    // regular process (e.g., because it calls sleep), and thus cannot
    // be run from main().
    first = 0;
    fsinit(0);
    file_init();
    fat32_init();
  }
  if(DEBUG_ELF){
    printk("in forkret\n");
    uvmprint(myproc()->pagetable);
  }
  usertrapret();
}


// Grow or shrink user memory by n bytes.
// Return 0 on success, -1 on failure.
int
growproc(int n)
{
  uint sz;
  struct proc *p = myproc();
  sz = p->sz;
  if(n > 0){
    if((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {
      return -1;
    }
  } else if(n < 0){
   panic("growproc in neg is not implement\n");
  }
  p->sz = sz;
  return 0;
}

// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
static struct proc*
allocproc(void)
{
  struct proc *p;

  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    if(p->state == UNUSED) {
      goto found;
    } else {
      release(&p->lock);
    }
  }
  return 0;

found:
  p->pid = allocpid();

  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)pmm_manager.alloc_pages(1)) == 0){
    // release(&p->lock);
    // return 0;
    panic("allocproc  p->trapframe  fail\n");
  }

  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  if(DEBUG_PROC){
    printk("allocproc   p->pagetable  %16lx\n",(uintptr_t)p->pagetable);
    uvmprint(p->pagetable);
  }
  if(p->pagetable == 0){
    // freeproc(p);
    // release(&p->lock);
    // return 0;
     panic("allocproc  p->pagetable  fail\n");
  }

  // Set up new context to start executing at forkret,
  // which returns to user space.
  memset(&p->context, 0, sizeof(p->context));
  p->context.ra = (uint64)forkret;
  p->context.sp = p->kstack + PGSIZE;



  return p;
}


void firstUserProcessInit(){
  struct proc *p;
  p = allocproc();
  initproc = p;
  init_elf_msg_by_argv0(&current_elf_msg,p);
  p->state=RUNNABLE;

  for(int i = 0; i < 3; i++){
     file_t * f = file_get(i);
     p->ofile[i]=f;
  }

  if(DEBUG_FILE){
       printk("firstUserProcessInit-----------\n");
       printProcFiles(p);
  }
  release(&p->lock);
  if(DEBUG_PROC)
    uvmprint(p->pagetable);
  p->cwd = ename("/");
  if(DEBUG_INODE){
    printk("in firstUserProcessInit finish namei\n");
  }
}

bool shutdown_flag=0;
// Per-CPU process scheduler.
// Each CPU calls scheduler() after setting itself up.
// Scheduler never returns.  It loops, doing;
//  - choose a process to run.
//  - swtch to start running that process.
//  - eventually that process transfers control
//    via swtch back to the scheduler.
void
scheduler(void)
{
  printk("in scheduler\n");
  struct proc *p;
  struct cpu *c = mycpu();

  c->proc = 0;
  for(;;){
    if(DEBUG_SCHE){
      printk("in scheduler\n");
    }

    if(NEED_SHUTDOWN){
      shutdown_flag=0;
      for(p = proc; p < &proc[NPROC]; p++) {
        acquire(&p->lock);
         if(p->state == RUNNABLE || p->state == SLEEPING){
            shutdown_flag=1;
         }
        release(&p->lock);
      }
      if(shutdown_flag==0){
        shutdown(0);
      }
    }
    // Avoid deadlock by ensuring that devices can interrupt.
    intr_on();
    for(p = proc; p < &proc[NPROC]; p++) {
      acquire(&p->lock);
      if(p->state == RUNNABLE) {
        // Switch to chosen process.  It is the process's job
        // to release its lock and then reacquire it
        // before jumping back to us.
        p->state = RUNNING;
        c->proc = p;
        swtch(&c->context, &p->context);

        // Process is done running for now.
        // It should have changed its p->state before coming back.
        c->proc = 0;
        if(DEBUG_SCHE){
          printk("in scheduler loop ra %p\n",r_ra());
        }
      }
      release(&p->lock);
    }
  }
}

// Atomically release lock and sleep on chan.
// Reacquires lock when awakened.
void
do_sleep(void *chan, spinlock_t *lk){
  struct proc *p = myproc();
  if(DEBUG_SCHE){
    printk("proc %d sleep on %p \n",p->pid,chan);
  }
  // Must acquire p->lock in order to
  // change p->state and then call sched.
  // Once we hold p->lock, we can be
  // guaranteed that we won't miss any wakeup
  // (wakeup locks p->lock),
  // so it's okay to release lk.
  if(lk != &p->lock){  //DOC: sleeplock0
    acquire(&p->lock);  //DOC: sleeplock1
    release(lk);
  }

  // Go to sleep.
  p->chan = chan;
  p->state = SLEEPING;

  sched();
  if(DEBUG_SCHE){
    printk("in sleep return pid = %d \n",p->pid);
  }
  // Tidy up.
  p->chan = 0;

  // Reacquire original lock.
  if(lk != &p->lock){
    release(&p->lock);
    acquire(lk);
  }
}


void proc_trapframe_print(struct proc * p){
    dump_tf(p->trapframe);
}

// free a proc structure and the data hanging from it,
// including user pages.
// p->lock must be held.
static void
freeproc(struct proc *p)
{
  if(p->pagetable)
    proc_freepagetable(p->pagetable, p->sz,(uint64)p->trapframe);
  p->trapframe=0;
  p->pagetable = 0;
  p->sz = 0;
  p->pid = 0;
  p->parent = 0;
  p->name[0] = 0;
  p->chan = 0;
  p->killed = 0;
  p->xstate = 0;
  p->state = UNUSED;
}


// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
  int i, pid;
  struct proc *np;
  struct proc *p = myproc();

  // Allocate process.
  if((np = allocproc()) == 0){
    return -1;
  }

  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    panic("fork uvmcopy failed \n");
    freeproc(np);
    release(&np->lock);
    return -1;
  }

  if(DEBUG_ELF){
    printk("in do fork copy pagetable\n");
    uvmprint(p->pagetable);
    printk("parent sz %p\n",p->sz);
    uvmprint(np->pagetable);
  }
  np->sz = p->sz;

  np->parent = p;

  // copy saved user registers.
  *(np->trapframe) = *(p->trapframe);

  // Cause fork to return 0 in the child.
  np->trapframe->a0 = 0;
  if(DEBUG_FILE){
       printk("before fork -----------\n");
       printProcFiles(p);
  }
  // increment reference counts on open file descriptors.
  for(i = 0; i < NOFILE; i++)
    if(p->ofile[i]!=0){
       int fd=i;
       file_t * f=file_proc_get(fd,p);
       np->ofile[i] = p->ofile[i];
    }

  if(DEBUG_FILE){
       printk("after fork -----------\n");
       printProcFiles(p);
  }

  np->cwd = edup(p->cwd);



  safestrcpy(np->name, p->name, sizeof(p->name));

  pid = np->pid;

  np->state = RUNNABLE;

  release(&np->lock);

  return pid;
}
// Wake up p if it is sleeping in wait(); used by exit().
// Caller must hold p->lock.
static void
wakeup1(struct proc *p)
{
  if(!holding(&p->lock))
    panic("wakeup1");
  if(p->chan == p && p->state == SLEEPING) {
    p->state = RUNNABLE;

    if(DEBUG_SCHE){
      printk("wake up %d  p->state==RUNNAVLE %d \n",p->pid,p->state==RUNNABLE);
    }
  }
}



// Switch to scheduler.  Must hold only p->lock
// and have changed proc->state. Saves and restores
// intena because intena is a property of this
// kernel thread, not this CPU. It should
// be proc->intena and proc->noff, but that would
// break in the few places where a lock is held but
// there's no process.
void
sched(void)
{



  int intena;
  struct proc *p = myproc();

  if(!holding(&p->lock))
    panic("sched p->lock");
  if(mycpu()->noff != 1)
    panic("sched locks");
  if(p->state == RUNNING)
    panic("sched running");
  if(intr_get())
    panic("sched interruptible");

  intena = mycpu()->intena;
  if(DEBUG_SCHE){
      printk("in shced before switch mycup ra %p  \n",mycpu()->context.ra);
  }
  swtch(&p->context, &mycpu()->context);
  if(DEBUG_SCHE){
      printk("in shced after switch \n");
  }
  mycpu()->intena = intena;
}

// Wake up all processes sleeping on chan.
// Must be called without any p->lock.
void
wakeup(void *chan)
{
  struct proc *p;

  for(p = proc; p < &proc[NPROC]; p++) {
    if(p != myproc()){
      acquire(&p->lock);
      if(p->state == SLEEPING && p->chan == chan) {
        p->state = RUNNABLE;
      }
      release(&p->lock);
    }
  }
}
// Wait for a child process to exit and return its pid.
// Return -1 if this process has no children.
int
wait(uint64 addr)
{
  struct proc *np;
  int havekids, pid;
  struct proc *p = myproc();

  // hold p->lock for the whole time to avoid lost
  // wakeups from a child's exit().
  acquire(&p->lock);

  for(;;){
    // Scan through table looking for exited children.
    havekids = 0;
    for(np = proc; np < &proc[NPROC]; np++){
      // this code uses np->parent without holding np->lock.
      // acquiring the lock first would cause a deadlock,
      // since np might be an ancestor, and we already hold p->lock.
      if(np->parent == p){
        // np->parent can't change between the check and the acquire()
        // because only the parent changes it, and we're the parent.
        acquire(&np->lock);
        havekids = 1;
        if(np->state == ZOMBIE){
          // Found one.
          pid = np->pid;
          if(addr != 0 && copyout(p->pagetable, addr, (char *)&np->xstate,
                                  sizeof(np->xstate)) < 0) {
            release(&np->lock);
            release(&p->lock);
            return -1;
          }
        if(DEBUG_SCHE){
          printk("addr %p\n",addr);
          printk("np pid %d state==XOMBIE %d\n",np->pid,np->state==ZOMBIE);
        }


          freeproc(np);
          release(&np->lock);
          release(&p->lock);

          if(DEBUG_SCHE){
            printk("addr %p\n",addr);
            printk("np pid %d state==XOMBIE %d\n",np->pid,np->state==ZOMBIE);
          }

          return pid;
        }
        release(&np->lock);
      }
    }

    // No point waiting if we don't have any children.
    if(!havekids || p->killed){
      release(&p->lock);
      return -1;
    }

    // Wait for a child to exit.
    do_sleep(p, &p->lock);  //DOC: wait-sleep
  }
}


// Exit the current process.  Does not return.
// An exited process remains in the zombie state
// until its parent calls wait().
void
exit(int status)
{
  if(DEBUG_SCHE){
    printk("in exit \n");
  }
  struct proc *p = myproc();

  // if(p == initproc)
  //   panic("init exiting");

  // Close all open files.
  for(int fd = 0; fd < NOFILE; fd++){
    if(p->ofile[fd]!=0){
        fd_proc_close(fd, p);
      // fd_close(fd);
      // p->ofile[fd] = 0;
    }
  }

  // begin_op();
  // iput(p->cwd);
  // end_op();
  // p->cwd = 0;

  // we might re-parent a child to init. we can't be precise about
  // waking up init, since we can't acquire its lock once we've
  // acquired any other proc lock. so wake up init whether that's
  // necessary or not. init may miss this wakeup, but that seems
  // harmless.
  // acquire(&initproc->lock);
  // wakeup1(initproc);
  // release(&initproc->lock);

  // grab a copy of p->parent, to ensure that we unlock the same
  // parent we locked. in case our parent gives us away to init while
  // we're waiting for the parent lock. we may then race with an
  // exiting parent, but the result will be a harmless spurious wakeup
  // to a dead or wrong process; proc structs are never re-allocated
  // as anything else.
  acquire(&p->lock);
  struct proc *original_parent = p->parent;
  release(&p->lock);

  // we need the parent's lock in order to wake it up from wait().
  // the parent-then-child rule says we have to lock it first.
  if(original_parent!=0)
     acquire(&original_parent->lock);

  acquire(&p->lock);

  // Give any children to init.
  // reparent(p);

  // Parent might be sleeping in wait().
  if(original_parent!=0)
    wakeup1(original_parent);

  p->xstate = status;
  p->state = ZOMBIE;

  if(original_parent!=0)
     release(&original_parent->lock);

  // Jump into the scheduler, never to return.
  sched();
  panic("zombie exit");
}

// Copy to either a user address, or kernel address,
// depending on usr_dst.
// Returns 0 on success, -1 on error.
int
either_copyout(int user_dst, uint64 dst, void *src, uint64 len)
{
  struct proc *p = myproc();
  if(user_dst){
    return copyout(p->pagetable, dst, src, len);
  } else {
    memmove((char *)dst, src, len);
    return 0;
  }
}

// Copy from user to kernel.
// Copy len bytes to dst from virtual address srcva in a given page table.
// Return 0 on success, -1 on error.
int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{
  uint64 n, va0, pa0;

  while(len > 0){
    va0 = PGROUNDDOWN(srcva);
    pte_t * pte = walk_user_pagetable(pagetable, va0,0);
    pa0=PTE2PA(*pte);
    if(pa0 == 0)
      return -1;
    n = PGSIZE - (srcva - va0);
    if(n > len)
      n = len;
    memmove(dst, (void *)(pa0 + (srcva - va0)), n);

    len -= n;
    dst += n;
    srcva = va0 + PGSIZE;
  }
  return 0;
}
// Copy from either a user address, or kernel address,
// depending on usr_src.
// Returns 0 on success, -1 on error.
int
either_copyin(void *dst, int user_src, uint64 src, uint64 len)
{
  struct proc *p = myproc();
  if(user_src){
    return copyin(p->pagetable, dst, src, len);
  } else {
    memmove(dst, (char*)src, len);
    return 0;
  }
}